#region license
# This file is part of Grawlix.
# 
# Grawlix is free software: you can redistribute it and/or modify
# it under the terms of the GNU General Public License as
# published by the Free Software Foundation, either version 3 of
# the License, or (at your option) any later version.
# 
# Grawlix is distributed in the hope that it will be useful,
# but WITHOUT ANY WARRANTY; without even the implied warranty of
# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
# GNU General Public License for more details.
# 
# You should have received a copy of the GNU General Public
# License along with Grawlix.  If not, see
# <http://www.gnu.org/licenses/>.
#endregion

"""Simplified rendering facade."""

namespace Grawlix.Services.Rendering
import Grawlix
import Grawlix.Meta
import Grawlix.Services
import ConcurrentBoo
import System
import System.Drawing.Color as Color
import OpenTK.Graphics

[Disposable]
transient final class GLRenderDevice(ServiceBase, IRenderDevice):
"""Rendering controls."""
	
	def constructor():
	"""Initial setup."""
		_affinity = ThreadAffinity()
		GL.Enable(EnableCap.Texture2D)
	
	final def LoadTexture([required] image as Image):
	"""
	Load the specified bitmap into a texture, getting an instance of texture data.
	Param image: The image to load.
	Returns: The texture state data that can be used to render.
	Remarks: This is an advanced operation.
		Use <Services.Rendering.TextureResource>.
	"""
		return GLTextureState(image)
	
	final def LoadFont([required] font as Drawing.Font):
	"""
	Load the specified font into the engine, getting an instance of FontData.
	Param font: The font to load.
	Returns: The font state data that can be used to render.
	Remarks: This is an advanced operation.
		Use <Services.Rendering.FontResource>.
	"""
		return GLFontState(font)
	
	final def UnloadTexture([required] texture as ITextureState):
	"""
	Unloads the specified texture object.
	Param texture: The texture data, originally returned by LoadTexture.
	Raises ArgumentException: texture is not a loaded texture object.
	Remarks: This is an advanced operation.
		Use <Services.Rendering.TextureResource>.
	"""
		glTexture = texture as GLTextureState
		if glTexture is null:
			raise ArgumentException(
				"Incompatible texture state.",
				"texture")
		glTexture.Unload()
	
	final def UnloadFont([required] font as IFontState):
	"""
	Unloads the specified font object.
	Param font: The font data, originally returned by LoadFont.
	Raises ArgumentException: font is not a loaded font object.
	Remarks: This is an advanced operation.
		Use <Services.Rendering.FontResource>.
	"""
		glFont = font as GLFontState
		if glFont is null:
			raise ArgumentException(
				"Incompatible font state.",
				"font")
		glFont.Unload()
	
	final def Resize(dimensions as Vector):
	"""
	Resize the viewport.
	Param dimensions: The new dimensions of the viewport.
	"""
		_affinity.Check()
		GL.Viewport(0, 0, dimensions.X, dimensions.Y)
		GL.MatrixMode(MatrixMode.Projection)
		GL.LoadIdentity()
		GL.Ortho(0, dimensions.X, 0, dimensions.Y, -1, 1)
	
	final def Clear():
	"""
	Clear the screen and set up view state.
	Remarks: Call once every frame before rendering).
	"""
		_affinity.Check()
		GL.Clear(ClearBufferMask.ColorBufferBit |
		         ClearBufferMask.StencilBufferBit)
	
	final def Finish():
	"""
	Wait for all rendering commands to be processed.
	Remarks: Call once every frame after rendering.
	"""
		_affinity.Check()
		GL.Flush()
		GL.Finish()
	
	final def DrawRectangle(color as Color, target as Rect):
	"""
	Draw a rectangle on the current context.
	Param color: The color to render.
	Param target: Where to render it.
	"""
		_affinity.Check()
		GL.Color4(color)
		GL.Rect(target.Left, target.Bottom, target.Right, target.Top)
	
	final def DrawTexture(
		[required] texture as ITextureState,
		source as Rect,
		tint as Color,
		target as Rect):
	"""
	Draw a textured on the current context.
	Param texture: The data of the texture to render, loaded by this renderer.
	Param source: The rectangle within the texture to render.
	Param tint: The color to tint the rectangle.
	Param target: The rectangle on the screen to render.
	Raises ArgumentException: texture was not created by
		this device or has been unloaded.
	"""
		_affinity.Check()
		glTexture as GLTextureState = texture
		if glTexture is null:
			raise ArgumentException(
				"Incompatible texture state.",
				"texture")
		GL.Color4(tint)
		glTexture.Bind()
		GL.Begin(BeginMode.Quads)
		GL.TexCoord2(source.Left,  source.Bottom)
		GL.Vertex2  (target.Left,  target.Bottom)
		GL.TexCoord2(source.Right, source.Bottom)
		GL.Vertex2  (target.Right, target.Bottom)
		GL.TexCoord2(source.Right, source.Top)
		GL.Vertex2  (target.Right, target.Top)
		GL.TexCoord2(source.Left,  source.Top)
		GL.Vertex2  (target.Left,  target.Top)
		GL.End()
	
	final def DrawText(
		[required] runs as TextRun*,
		origin as Vector):
	"""
	Render text in a box on the screen.
	Param runs: The text to render.
	Param origin: An origin point relative to the glyph locations.
	Raises ArgumentException: The font of a run was not created by
		this device or has been unloaded.
	"""
		_affinity.Check()
		for run in runs:
			DrawText(run, origin)
	
	private def DrawText(
		run as TextRun,
		origin as Vector):
	"""
	Render a single run of text in a box on the screen.
	Param run: The text to render.
	Param origin: An origin point relative to the glyph locations.
	"""
		glFont = run.Font as GLFontState
		if glFont is null:
			raise ArgumentException(
				"Incompatible font state.",
				"font")
		# TODO: Implement text rendering.
	
	final def Clip(area as Rect) as IDisposable:
	"""
	Clip to the specified area.
	Returns: An object used to undo the operation.
		Put it in a using block.
	"""
		_affinity.Check()
		clip = ClippingRaii(self)
		clip.Apply(area)
		return clip
	
	final def Zoom(source as Rect, scale as Rect) as IDisposable:
	"""
	Change the view perspective, clipping to the new screen.
	Param source: The rectangle to scale and clip to, in
		old coordinates.
	Param scale: The new coordinates of the screen.
	Returns: An object used to undo the operation.
		Put it in a using block.
	"""
		_affinity.Check()
		zoom = ZoomingRaii(self)
		zoom.Apply(source, scale)
		return zoom
	
	[getter(Affinity)]
	private _affinity as ThreadAffinity
	"""Checks that the appropriate thread is maintained."""
	
	private final class ClippingRaii(IDisposable):
	"""
	A class to use in a using block
	to handle clipping and reversing clipping.
	"""
		def constructor(render as GLRenderDevice):
		"""
		Clip to the rect.
		Param render: The device to use.
		"""
			_render = render
		
		def Apply(area as Rect):
		"""
		Apply the clipping.
		Param area: The area to clip to.
		"""
			_render.Affinity.Check()
			_area = area
			# TODO: Implement clipping
		
		def Dispose():
		"""Undo the clipping."""
			if _render is null:
				return
			_render.Affinity.Check()
			# TODO: Implement declipping
			_render = null
		
		private _render as GLRenderDevice
		"""Device to render to."""
		
		private _area as Rect
		"""Area of the clipped rect."""
	
	private final class ZoomingRaii(IDisposable):
	"""
	A class to use in a using block
	to handle perspective change and undo it.
	"""
		def constructor(render as GLRenderDevice):
		"""
		Zoom the src area to the dest area.
		Param render: The device to render to.
		"""
			_render = render
		
		def Apply(source as Rect, scale as Rect):
		"""
		Apply the zoom.
		Param source: The area in original coordinates.
		Param scale: The new coordinates for src.
		"""
			_render.Affinity.Check()
			GL.MatrixMode(MatrixMode.Modelview)
			GL.PushMatrix()
			GL.Translate(-source.Left, -source.Bottom, 0.0)
			GL.Scale(1 / source.Width, 1 / source.Height, 1.0)
			GL.Scale(scale.Width, scale.Height, 1.0)
			GL.Translate(scale.Left, scale.Bottom, 0.0)
		
		def Dispose():
		"""Undo the zoom."""
			if _render is null:
				return
			_render.Affinity.Check()
			GL.MatrixMode(MatrixMode.Modelview)
			GL.PopMatrix()
			_render = null
		
		private _render as GLRenderDevice
		"""Device to render to."""
	
	
	
	
	
	
	
	
